#include <fs.h>

typedef size_t (*ReadFn) (void *buf, size_t offset, size_t len);
typedef size_t (*WriteFn) (const void *buf, size_t offset, size_t len);

typedef struct {
  char *name;
  size_t size;
  size_t disk_offset;
  size_t open_offset;
  ReadFn read;
  WriteFn write;
} Finfo;

enum {FD_STDIN, FD_STDOUT, FD_STDERR, FD_EVENT, FD_DISPINFO, FD_FB};
size_t ramdisk_read(void *buf, size_t offset, size_t len);
size_t events_read(void *buf, size_t offset, size_t len);
size_t dispinfo_read(void *buf, size_t offset, size_t len);
size_t ramdisk_write(const void *buf, size_t offset, size_t len);
size_t serial_write(const void *buf, size_t offset, size_t len);
size_t fb_write(const void *buf, size_t offset, size_t len);


size_t invalid_read(void *buf, size_t offset, size_t len) {
  panic("should not reach here");
  return 0;
}

size_t invalid_write(const void *buf, size_t offset, size_t len) {
  panic("should not reach here");
  return 0;
}

/* This is the information about all files in disk. */
static Finfo file_table[] __attribute__((used)) = {
  [FD_STDIN]  = {"stdin" , 0, 0, 0, invalid_read, invalid_write},
  [FD_STDOUT] = {"stdout", 0, 0, 0, invalid_read, serial_write},
  [FD_STDERR] = {"stderr", 0, 0, 0, invalid_read, serial_write},
  [FD_EVENT]  = {"/dev/events", 0, 0, 0, events_read, invalid_write},
  [FD_DISPINFO]  = {"/proc/dispinfo", 0, 0, 0, dispinfo_read, invalid_write},
  [FD_FB]     = {"/dev/fb", 0, 0, 0, invalid_read, fb_write},
#include "files.h"
};

void init_fs() {
  // TODO: initialize the size of /dev/fb
  file_table[FD_FB].size = 4 * 400 * 300; //bytes, a pixel = uint32 which equals to 4 byte 
}

int fs_open(const char *pathname, int flags, int mode){
for(int i  = 0; i < sizeof(file_table)/sizeof(Finfo);i++){
  while(!(strcmp(pathname,file_table[i].name))){
//    printf("Do num: %d %s\n",i,file_table[i].name);
      file_table[i].open_offset = 0;
      return i;
    }
  }
  assert(0);
}

size_t fs_read(int fd, void *buf, size_t len){
  int ret;
  if(file_table[fd].read != NULL){
    ret = file_table[fd].read(buf,file_table[fd].disk_offset+file_table[fd].open_offset,len); 
  }
  else{
    if(len+file_table[fd].open_offset > file_table[fd].size){
//    Log("READ OUT OF LENGTH len:%d,open_offset:%d",len,file_table[fd].open_offset);
    len = file_table[fd].size - file_table[fd].open_offset;
    }
    ret = ramdisk_read(buf,file_table[fd].disk_offset+file_table[fd].open_offset,len);
  }
  file_table[fd].open_offset += ret;
  return ret;
}

size_t fs_write(int fd, const void *buf, size_t len){
  int ret;
  if(file_table[fd].write != NULL){
   ret = file_table[fd].write(buf,file_table[fd].disk_offset+file_table[fd].open_offset,len); 
  }
  else{
    if(len+file_table[fd].open_offset > file_table[fd].size){
  //    Log("WRITE OUT OF LENGTHlen:%d,open_offset:%d",len,file_table[fd].open_offset);
      len = file_table[fd].size - file_table[fd].open_offset;
    }//assert file write length
    ret = ramdisk_write(buf,file_table[fd].disk_offset+file_table[fd].open_offset,len);
  }
  file_table[fd].open_offset += ret;
  return ret;
}

size_t fs_lseek(int fd, size_t offset, int whence){
  if(whence == SEEK_SET){
    assert(offset < file_table[fd].size);
    file_table[fd].open_offset = offset; 
    return file_table[fd].open_offset;
  }
  else if(whence == SEEK_CUR){
    //assert(offset+file_table[fd].open_offset < file_table[fd].size);
    if(offset+file_table[fd].open_offset > file_table[fd].size){
//    Log("SEEK_CUR OUT OF LENGTHlen:%d,open_offset:%d",offset,file_table[fd].open_offset);
    file_table[fd].open_offset = file_table[fd].size;
    }
    file_table[fd].open_offset += offset; 
    return file_table[fd].open_offset;
  }
  else if(whence == SEEK_END){
    assert(!offset);
    file_table[fd].open_offset =  file_table[fd].size;
    return file_table[fd].open_offset;
  }
  else assert(0);
}

size_t fs_ltell(int fd){
 return file_table[fd].open_offset;
}

int fs_close(int fd){
  return 0;
}
